page.tsx 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144
  1. "use client";
  2. import { useEffect, useState } from "react";
  3. import { useTranslations } from "next-intl";
  4. import { Link } from "@/i18n/navigation";
  5. import { useAuth } from "@/providers/auth-provider";
  6. import { History, ArrowLeft, Loader2 } from "lucide-react";
  7. import {
  8. fetchWithdrawalList,
  9. getWithdrawalStatusLabel,
  10. type WithdrawalRecord,
  11. } from "@/lib/withdrawal-api";
  12. import { cn } from "@/lib/utils";
  13. const PAGE_SIZE = 10;
  14. export default function AccountWithdrawalsPage() {
  15. const t = useTranslations("account");
  16. const { user, isReady } = useAuth();
  17. const [records, setRecords] = useState<WithdrawalRecord[]>([]);
  18. const [loading, setLoading] = useState(false);
  19. const [error, setError] = useState<string | null>(null);
  20. const [page, setPage] = useState(1);
  21. useEffect(() => {
  22. if (!user) return;
  23. let cancelled = false;
  24. async function loadRecords() {
  25. setLoading(true);
  26. setError(null);
  27. try {
  28. const list = await fetchWithdrawalList({ current: page, row: PAGE_SIZE });
  29. if (cancelled) return;
  30. setRecords(list);
  31. } catch (e) {
  32. if (cancelled) return;
  33. setError((e as Error).message || "领取记录加载失败,请稍后重试。");
  34. setRecords([]);
  35. } finally {
  36. if (!cancelled) setLoading(false);
  37. }
  38. }
  39. void loadRecords();
  40. return () => { cancelled = true; };
  41. }, [user, page]);
  42. if (!isReady) return <div className="min-h-screen bg-[#050b14] flex items-center justify-center text-slate-500"><Loader2 className="animate-spin" /></div>;
  43. if (!user) {
  44. return (
  45. <div className="min-h-screen bg-[#050b14] flex items-center justify-center px-4 font-sans">
  46. <div className="w-full max-w-md rounded-[2.5rem] border border-white/10 bg-white/5 p-10 text-center backdrop-blur-2xl">
  47. <h2 className="text-2xl font-bold text-white mb-6">您尚未登录</h2>
  48. <Link href="/auth/login" className="inline-block rounded-full bg-[#f3deae] px-10 py-4 text-sm font-bold text-[#5c461a] shadow-lg">立即登录</Link>
  49. </div>
  50. </div>
  51. );
  52. }
  53. const hasPrev = page > 1;
  54. const hasNext = records.length >= PAGE_SIZE;
  55. function getStatusStyle(status: number | string) {
  56. const s = String(status);
  57. if (s === "2" || s === "3") return "text-emerald-400 border-emerald-400/30 bg-emerald-400/10";
  58. if (s === "4" || s === "5") return "text-rose-400 border-rose-400/30 bg-rose-400/10";
  59. return "text-amber-400 border-amber-400/30 bg-amber-400/10";
  60. }
  61. return (
  62. <div className="min-h-screen bg-[#050b14] pb-24 text-slate-300 font-sans relative">
  63. <div className="pointer-events-none fixed inset-0 z-0">
  64. <div className="absolute left-1/4 top-0 h-[500px] w-[500px] rounded-full bg-blue-900/10 blur-[120px]" />
  65. <div className="absolute right-1/4 bottom-0 h-[500px] w-[500px] rounded-full bg-[#b89458]/5 blur-[120px]" />
  66. </div>
  67. <div className="site-container relative z-10 pt-16">
  68. <div className="flex items-center justify-between mb-8">
  69. <div>
  70. <h1 className="font-serif text-3xl font-bold text-white">全部领取记录</h1>
  71. <p className="mt-2 text-sm text-slate-400">追踪您的每一笔资金变动状态</p>
  72. </div>
  73. <Link href="/account" className="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-5 py-2.5 text-sm font-semibold text-slate-300 transition hover:bg-white/10 hover:text-white backdrop-blur-md">
  74. <ArrowLeft size={16} /> 返回控制中心
  75. </Link>
  76. </div>
  77. <section className="rounded-[2.5rem] border border-white/10 bg-white/5 p-6 md:p-10 backdrop-blur-2xl shadow-2xl">
  78. {loading ? <div className="py-20 flex justify-center text-slate-500"><Loader2 className="animate-spin h-8 w-8" /></div> : null}
  79. {error ? <div className="mb-6 rounded-2xl border border-rose-500/20 bg-rose-500/10 p-4 text-sm text-rose-400">{error}</div> : null}
  80. {!loading && !error && records.length === 0 ? (
  81. <div className="py-20 flex flex-col items-center justify-center rounded-[2rem] border border-dashed border-white/10 bg-white/5 text-slate-500">
  82. <History size={48} className="mb-4 opacity-50" />
  83. <p className="text-sm">暂无领取记录。</p>
  84. </div>
  85. ) : null}
  86. {!loading && !error && records.length > 0 ? (
  87. <div className="space-y-4">
  88. {records.map((w) => (
  89. <div key={w.serial} className="group rounded-[1.5rem] border border-white/5 bg-white/5 p-6 transition-all hover:bg-white/10">
  90. <div className="flex flex-col md:flex-row md:items-center justify-between gap-6">
  91. <div className="flex items-start gap-5">
  92. <div className="flex h-12 w-12 shrink-0 items-center justify-center rounded-2xl bg-white/5 text-teal-400">
  93. <History size={22} />
  94. </div>
  95. <div>
  96. <p className="text-base font-bold text-white group-hover:text-teal-400 transition-colors">{w.details}</p>
  97. <div className="mt-2 flex flex-wrap items-center gap-x-4 gap-y-2 text-xs font-medium text-slate-500">
  98. <span>流水号: {w.serial}</span>
  99. <span className="hidden sm:inline">•</span>
  100. <span>申请: {w.addTime || "-"}</span>
  101. {w.payTime && <><span className="hidden sm:inline">•</span><span>到账: {w.payTime}</span></>}
  102. </div>
  103. </div>
  104. </div>
  105. <div className="flex items-center justify-between md:justify-end gap-6 md:border-l md:border-white/5 md:pl-6">
  106. <div className="text-right">
  107. <p className="text-xl font-bold text-white tracking-tight">${w.amount}</p>
  108. <span className={cn("inline-block mt-1.5 rounded-full border px-3 py-1 text-[10px] font-bold uppercase tracking-widest", getStatusStyle(w.status))}>
  109. {getWithdrawalStatusLabel(w.status)}
  110. </span>
  111. </div>
  112. </div>
  113. </div>
  114. </div>
  115. ))}
  116. </div>
  117. ) : null}
  118. {/* 分页 */}
  119. {!loading && !error && (hasPrev || hasNext) && (
  120. <div className="mt-10 flex items-center justify-center gap-4 border-t border-white/5 pt-8">
  121. <button onClick={() => setPage(p => Math.max(1, p - 1))} disabled={!hasPrev || loading} className="rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm font-semibold transition hover:bg-white/10 disabled:opacity-30">上一页</button>
  122. <span className="text-sm font-medium text-slate-500">第 <span className="text-white">{page}</span> 页</span>
  123. <button onClick={() => setPage(p => p + 1)} disabled={!hasNext || loading} className="rounded-xl border border-white/10 bg-white/5 px-4 py-2 text-sm font-semibold transition hover:bg-white/10 disabled:opacity-30">下一页</button>
  124. </div>
  125. )}
  126. </section>
  127. </div>
  128. </div>
  129. );
  130. }